#pragma once

#include <charconv>
#include <stdexcept>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "rapidjson/document.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "rapidjson/error/en.h"
#include "table_interface.h"
#include "table_base.h"
#include "CxxFeature.h"

namespace StringHelper {
    template<typename T> std::string ToString(T entity) {
        return std::to_string(entity);
    }
#define TO_STRING(TYPE,SETTER) \
    template<> inline std::string ToString(TYPE entity) { \
        return SETTER; \
    }
    TO_STRING(char, std::string(1, entity))
    TO_STRING(bool, std::string(1, entity ? '1' : '0'))
#undef TO_STRING
#define TO_STRING(TYPE,FORMAT) \
    template<> inline std::string ToString(TYPE entity) { \
        char s[128]; \
        int n = std::snprintf(s, sizeof(s), FORMAT, entity); \
        return std::string(s, n); \
    }
    TO_STRING(float, "%g")
    TO_STRING(double, "%g")
    TO_STRING(long double, "%Lg")
#undef TO_STRING
    inline const std::string &ToString(const std::string &entity) {
        return entity;
    }

    template<typename T> void FromString(T &entity, const std::string_view &value) {
        std::from_chars(value.data(), value.data() + value.size(), entity);
    }
#define FROM_STRING(TYPE,GETTER) \
    template<> inline void FromString(TYPE &entity, const std::string_view &value) { \
        entity = GETTER; \
    }
    FROM_STRING(char, !value.empty() ? value[0] : '\0')
    FROM_STRING(bool, !value.empty() ? value[0] != '0' : false)
    FROM_STRING(std::string, value)
#undef FROM_STRING
}

namespace StreamHelper {
    template<typename T> void ResizeWithPeek(T &entity, std::streamsize number, const std::istream &stream) {
        if (stream.rdbuf()->in_avail() >= number && number >= 0) { entity.clear(); entity.resize(number); }
        else { throw std::out_of_range("resize parameter is too large."); }
    }
    template<typename T> void ReserveWithPeek(T &entity, std::streamsize number, const std::istream &stream) {
        if (stream.rdbuf()->in_avail() >= number && number >= 0) { entity.clear(); entity.reserve(number); }
        else { throw std::out_of_range("reserve parameter is too large."); }
    }

    template<typename T> void FromStream(T &value, std::istream &stream) {
        stream.read((char *)&value, sizeof(value));
    }
    template<typename T> void ToStream(const T &value, std::ostream &stream) {
        stream.write((const char *)&value, sizeof(value));
    }
    template<> inline void FromStream(std::string &value, std::istream &stream) {
        std::size_t size = TableBase::FromStream7BitEncodedInt(stream);
        ResizeWithPeek(value, size, stream);
        stream.read(&value[0], size);
    }
    template<> inline void ToStream(const std::string &value, std::ostream &stream) {
        std::size_t size = value.size();
        TableBase::ToStream7BitEncodedInt(size, stream);
        stream.write(value.data(), size);
    }
    inline void FromStream(std::vector<bool>::reference value, std::istream &stream) {
        bool bvalue = false;
        FromStream(bvalue, stream);
        value = bvalue;
    }

    template<typename T> void EnumFromStream(T &entity, std::istream &stream) {
        int32 value = 0;
        FromStream(value, stream);
        entity = static_cast<T>(value);
    }
    template<typename T> void EnumToStream(T entity, std::ostream &stream) {
        ToStream(static_cast<int32>(entity), stream);
    }

    template<typename T> void SequenceFromStream(std::vector<T> &entity, std::istream &stream) {
        std::size_t number = TableBase::FromStream7BitEncodedInt(stream);
        ResizeWithPeek(entity, number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            FromStream(entity[index], stream);
        }
    }
    template<typename T> void SequenceToStream(const std::vector<T> &entity, std::ostream &stream) {
        std::size_t number = entity.size();
        TableBase::ToStream7BitEncodedInt(number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            ToStream(entity[index], stream);
        }
    }

    template<typename T> void BlockSequenceFromStream(std::vector<T> &entity, std::istream &stream) {
        std::size_t number = TableBase::FromStream7BitEncodedInt(stream);
        ResizeWithPeek(entity, number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            LoadFromStream(entity[index], stream);
        }
    }
    template<typename T> void BlockSequenceToStream(const std::vector<T> &entity, std::ostream &stream) {
        std::size_t number = entity.size();
        TableBase::ToStream7BitEncodedInt(number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            SaveToStream(entity[index], stream);
        }
    }

    template<typename K, typename T> void AssociativeFromStream(std::unordered_map<K, T> &entity, std::istream &stream) {
        std::size_t number = TableBase::FromStream7BitEncodedInt(stream);
        ReserveWithPeek(entity, number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            std::pair<K, T> pair;
            FromStream(pair.first, stream);
            FromStream(pair.second, stream);
            entity.insert(std::move(pair));
        }
    }
    template<typename K, typename T> void AssociativeToStream(const std::unordered_map<K, T> &entity, std::ostream &stream) {
        std::size_t number = entity.size();
        TableBase::ToStream7BitEncodedInt(number, stream);
        for (auto &pair : entity) {
            ToStream(pair.first, stream);
            ToStream(pair.second, stream);
        }
    }

    template<typename K, typename T> void BlockAssociativeFromStream(std::unordered_map<K, T> &entity, std::istream &stream) {
        std::size_t number = TableBase::FromStream7BitEncodedInt(stream);
        ReserveWithPeek(entity, number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            std::pair<K, T> pair;
            FromStream(pair.first, stream);
            LoadFromStream(pair.second, stream);
            entity.insert(std::move(pair));
        }
    }
    template<typename K, typename T> void BlockAssociativeToStream(const std::unordered_map<K, T> &entity, std::ostream &stream) {
        std::size_t number = entity.size();
        TableBase::ToStream7BitEncodedInt(number, stream);
        for (auto &pair : entity) {
            ToStream(pair.first, stream);
            SaveToStream(pair.second, stream);
        }
    }

    template<typename T> void UniqueFromStream(std::unordered_set<T> &entity, std::istream &stream) {
        std::size_t number = TableBase::FromStream7BitEncodedInt(stream);
        ReserveWithPeek(entity, number, stream);
        for (std::size_t index = 0; index < number; ++index) {
            T value;
            FromStream(value, stream);
            entity.insert(std::move(value));
        }
    }
    template<typename T> void UniqueToStream(const std::unordered_set<T> &entity, std::ostream &stream) {
        std::size_t number = entity.size();
        TableBase::ToStream7BitEncodedInt(number, stream);
        for (auto& value : entity) {
            ToStream(value, stream);
        }
    }
}

class JsonHelper {
public:
    JsonHelper(rapidjson::Document::AllocatorType &allocator) : allocator_(allocator) {}
    static void SetJsonObjectValue(rapidjson::Value &value) { value.SetObject(); }

    template<typename T> static void FromJson(T &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                FromJson(entity, itr->value);
            }
        }
    }
    template<typename T> void ToJson(const T &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        ToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename T> static void BlockFromJson(T &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                BlockFromJson(entity, itr->value);
            }
        }
    }
    template<typename T> void BlockToJson(const T &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        BlockToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename T> static void SequenceFromJson(std::vector<T> &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                SequenceFromJson(entity, itr->value);
            }
        }
    }
    template<typename T> void SequenceToJson(const std::vector<T> &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        SequenceToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename T> static void BlockSequenceFromJson(std::vector<T> &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                BlockSequenceFromJson(entity, itr->value);
            }
        }
    }
    template<typename T> void BlockSequenceToJson(const std::vector<T> &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        BlockSequenceToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename K, typename T> static void AssociativeFromJson(std::unordered_map<K, T> &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                AssociativeFromJson(entity, itr->value);
            }
        }
    }
    template<typename K, typename T> void AssociativeToJson(const std::unordered_map<K, T> &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        AssociativeToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename K, typename T> static void BlockAssociativeFromJson(std::unordered_map<K, T> &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                BlockAssociativeFromJson(entity, itr->value);
            }
        }
    }
    template<typename K, typename T> void BlockAssociativeToJson(const std::unordered_map<K, T> &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        BlockAssociativeToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename T> static void UniqueFromJson(std::unordered_set<T> &entity, const rapidjson::Value &parent, const char *name) {
        if (parent.IsObject()) {
            auto itr = parent.FindMember(name);
            if (itr != parent.MemberEnd()) {
                UniqueFromJson(entity, itr->value);
            }
        }
    }
    template<typename T> void UniqueToJson(const std::unordered_set<T> &entity, rapidjson::Value &parent, const char *name) {
        rapidjson::Value value;
        UniqueToJson(entity, value);
        parent.AddMember(rapidjson::StringRef(name), value, allocator_);
    }

    template<typename T> static void TableOptionalsFromJsonText(T &table, const std::string_view &text) {
        TableOptionalsFromJson(table, ParseJsonDocument(text));
    }
    template<typename T> static std::string TableOptionalsToJsonText(const T &table) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).TableOptionalsToJson(table, document);
        return JsonToText(document);
    }

    template<typename T> static void BlockFromJsonText(T &entity, const std::string_view &text) {
        BlockFromJson(entity, ParseJsonDocument(text));
    }
    template<typename T> static std::string BlockToJsonText(const T &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).BlockToJson(entity, document);
        return JsonToText(document);
    }

    template<typename T> static void SequenceFromJsonText(std::vector<T> &entity, const std::string_view &text) {
        SequenceFromJson(entity, ParseJsonDocument(text));
    }
    template<typename T> static std::string SequenceToJsonText(const std::vector<T> &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).SequenceToJson(entity, document);
        return JsonToText(document);
    }

    template<typename T> static void BlockSequenceFromJsonText(std::vector<T> &entity, const std::string_view &text) {
        BlockSequenceFromJson(entity, ParseJsonDocument(text));
    }
    template<typename T> static std::string BlockSequenceToJsonText(const std::vector<T> &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).BlockSequenceToJson(entity, document);
        return JsonToText(document);
    }

    template<typename K, typename T> static void AssociativeFromJsonText(std::unordered_map<K, T> &entity, const std::string_view &text) {
        AssociativeFromJson(entity, ParseJsonDocument(text));
    }
    template<typename K, typename T> static std::string AssociativeToJsonText(const std::unordered_map<K, T> &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).AssociativeToJson(entity, document);
        return JsonToText(document);
    }

    template<typename K, typename T> static void BlockAssociativeFromJsonText(std::unordered_map<K, T> &entity, const std::string_view &text) {
        BlockAssociativeFromJson(entity, ParseJsonDocument(text));
    }
    template<typename K, typename T> static std::string BlockAssociativeToJsonText(const std::unordered_map<K, T> &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).BlockAssociativeToJson(entity, document);
        return JsonToText(document);
    }

    template<typename T> static void UniqueFromJsonText(std::unordered_set<T> &entity, const std::string_view &text) {
        UniqueFromJson(entity, ParseJsonDocument(text));
    }
    template<typename T> static std::string UniqueToJsonText(const std::unordered_set<T> &entity) {
        rapidjson::Document document;
        JsonHelper(document.GetAllocator()).UniqueToJson(entity, document);
        return JsonToText(document);
    }

private:
    rapidjson::Document::AllocatorType &allocator_;

    static std::string JsonToText(const rapidjson::Value &value) {
        rapidjson::StringBuffer buffer;
        rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
        value.Accept(writer);
        return std::string(buffer.GetString(), buffer.GetSize());
    }

    static rapidjson::Document ParseJsonDocument(const std::string_view &text) {
        rapidjson::Document document;
        if (document.Parse(text.data(), text.size()).HasParseError()) {
            printf("JSON parse error: %s -->%d\n%.*s",
                rapidjson::GetParseError_En(document.GetParseError()),
                (int)document.GetErrorOffset(), (int)text.size(), text.data());
        }
        return document;
    }

    template<typename T> static void TableOptionalsFromJson(T &entity, const rapidjson::Value &value);
    template<typename T> void TableOptionalsToJson(const T &entity, rapidjson::Value &value);

    template<typename T> static void BlockFromJson(T &entity, const rapidjson::Value &value);
    template<typename T> void BlockToJson(const T &entity, rapidjson::Value &value);

    template<typename T> static void FromJson(T &entity, const rapidjson::Value &value);
    template<typename T> void ToJson(const T &entity, rapidjson::Value &value);
    static void FromJson(std::vector<bool>::reference entity, const rapidjson::Value &value) {
        if (value.IsBool()) entity = value.GetBool();
    }

    template<typename T> static void EnumFromJson(T &entity, const rapidjson::Value &value) {
        entity = static_cast<T>(value.IsInt() ? value.GetInt() : 0);
    }
    template<typename T> void EnumToJson(T entity, rapidjson::Value &value) {
        value.SetInt(static_cast<int32>(entity));
    }

    template<typename T> static void FromJsonString(T &entity, const rapidjson::Value &value) {
        if (value.IsString()) StringHelper::FromString(entity, {value.GetString(), value.GetStringLength()});
    }
    template<typename T> void ToJsonString(const T &entity, rapidjson::Value &value) {
        ToJson(StringHelper::ToString(entity), value);
    }

    template<typename T> static void SequenceFromJson(std::vector<T> &entity, const rapidjson::Value &value) {
        if (value.IsArray()) {
            size_t number = value.Size();
            entity.clear(), entity.resize(number);
            for (size_t index = 0; index < number; ++index) {
                FromJson(entity[index], value[rapidjson::SizeType(index)]);
            }
        }
    }
    template<typename T> void SequenceToJson(const std::vector<T> &entity, rapidjson::Value &value) {
        size_t number = entity.size();
        value.SetArray().Reserve(rapidjson::SizeType(number), allocator_);
        for (size_t index = 0; index < number; ++index) {
            rapidjson::Value gvalue;
            ToJson(entity[index], gvalue);
            value.PushBack(gvalue, allocator_);
        }
    }

    template<typename T> static void BlockSequenceFromJson(std::vector<T> &entity, const rapidjson::Value &value) {
        if (value.IsArray()) {
            size_t number = value.Size();
            entity.clear(), entity.resize(number);
            for (size_t index = 0; index < number; ++index) {
                BlockFromJson(entity[index], value[rapidjson::SizeType(index)]);
            }
        }
    }
    template<typename T> void BlockSequenceToJson(const std::vector<T> &entity, rapidjson::Value &value) {
        size_t number = entity.size();
        value.SetArray().Reserve(rapidjson::SizeType(number), allocator_);
        for (size_t index = 0; index < number; ++index) {
            rapidjson::Value gvalue;
            BlockToJson(entity[index], gvalue);
            value.PushBack(gvalue, allocator_);
        }
    }

    template<typename K, typename T> static void AssociativeFromJson(std::unordered_map<K, T> &entity, const rapidjson::Value &value) {
        if (value.IsObject()) {
            size_t number = value.MemberCount();
            entity.clear(), entity.reserve(number);
            for (auto itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
                std::pair<K, T> pair;
                FromJsonString(pair.first, itr->name);
                FromJson(pair.second, itr->value);
                entity.insert(std::move(pair));
            }
        }
    }
    template<typename K, typename T> void AssociativeToJson(const std::unordered_map<K, T> &entity, rapidjson::Value &value) {
        size_t number = entity.size();
        value.SetObject().MemberReserve(rapidjson::SizeType(number), allocator_);
        for (auto &pair : entity) {
            rapidjson::Value name, gvalue;
            ToJsonString(pair.first, name);
            ToJson(pair.second, gvalue);
            value.AddMember(name, gvalue, allocator_);
        }
    }

    template<typename K, typename T> static void BlockAssociativeFromJson(std::unordered_map<K, T> &entity, const rapidjson::Value &value) {
        if (value.IsObject()) {
            size_t number = value.MemberCount();
            entity.clear(), entity.reserve(number);
            for (auto itr = value.MemberBegin(); itr != value.MemberEnd(); ++itr) {
                std::pair<K, T> pair;
                FromJsonString(pair.first, itr->name);
                BlockFromJson(pair.second, itr->value);
                entity.insert(std::move(pair));
            }
        }
    }
    template<typename K, typename T> void BlockAssociativeToJson(const std::unordered_map<K, T> &entity, rapidjson::Value &value) {
        size_t number = entity.size();
        value.SetObject().MemberReserve(rapidjson::SizeType(number), allocator_);
        for (auto &pair : entity) {
            rapidjson::Value name, gvalue;
            ToJsonString(pair.first, name);
            BlockToJson(pair.second, gvalue);
            value.AddMember(name, gvalue, allocator_);
        }
    }

    template<typename T> static void UniqueFromJson(std::unordered_set<T> &entity, const rapidjson::Value &value) {
        if (value.IsArray()) {
            size_t number = value.Size();
            entity.clear(), entity.reserve(number);
            for (size_t index = 0; index < number; ++index) {
                T gvalue;
                FromJson(gvalue, value[rapidjson::SizeType(index)]);
                entity.insert(std::move(gvalue));
            }
        }
    }
    template<typename T> void UniqueToJson(const std::unordered_set<T> &entity, rapidjson::Value &value) {
        size_t number = entity.size();
        value.SetArray().Reserve(rapidjson::SizeType(number), allocator_);
        for (auto& value : entity) {
            rapidjson::Value gvalue;
            ToJson(value, gvalue);
            value.PushBack(gvalue, allocator_);
        }
    }
};

#define JSON_CONVERTER(TYPE,TNAME,VNAME) \
    template<> inline void JsonHelper::FromJson(TYPE &entity, const rapidjson::Value &value) { \
        if (value.Is##TNAME()) entity = static_cast<TYPE>(value.Get##VNAME()); \
    } \
    template<> inline void JsonHelper::ToJson(const TYPE &entity, rapidjson::Value &value) { \
        value.Set##VNAME(entity); \
    }
    JSON_CONVERTER(char, Int, Int)
    JSON_CONVERTER(bool, Bool, Bool)
    JSON_CONVERTER(float, Number, Double)
    JSON_CONVERTER(double, Number, Double)
    JSON_CONVERTER(int8, Int, Int)
    JSON_CONVERTER(uint8, Uint, Uint)
    JSON_CONVERTER(int16, Int, Int)
    JSON_CONVERTER(uint16, Uint, Uint)
    JSON_CONVERTER(int32, Int, Int)
    JSON_CONVERTER(uint32, Uint, Uint)
    JSON_CONVERTER(int64, Int64, Int64)
    JSON_CONVERTER(uint64, Uint64, Uint64)
#undef JSON_CONVERTER
template<> inline void JsonHelper::FromJson(std::string &entity, const rapidjson::Value &value) {
    if (value.IsString()) entity.assign(value.GetString(), value.GetStringLength());
}
template<> inline void JsonHelper::ToJson(const std::string &entity, rapidjson::Value &value) {
    value.SetString(entity.data(), rapidjson::SizeType(entity.size()), allocator_);
}
